import java.util.*;

/**
 * @class CalendarSeeker 
 * This program implements Task 4 from the "Java Basics" chapter of "Java SE" training:
 * Prints out the day of the week for a given day (1..31), month (1..12) and year. 
 * For example, for a given input "10 October 2010" it prints to the screen "Sunday".
 *
 * @version 1.0
 * @date 2013-06-18
 * @author Inna Ianko
 */
public class CalendarSeeker {

		/* 
		 * CalendarSeeker class will calculate day of week for inputed by user date  
		 * using the "Perpetual Calendar Formula for Gregorian Calendar". 
		 */
		 
	public static final String PROGRAM_DESCRIPTION = 
			"This program will print day of week for given date using Gregorian Calendar.";
	public static final String[] LABELS = {"year", "month", "day"};
	
	public static final int TEN_YEARS             			 = 10;
	public static final int CENTURY                          = 100;
	public static final int LEAP_YEAR_DIVIDER_IN_CENTURY     = 400;
	public static final int	LEAP_YEAR_DIVIDER_NOT_IN_CENTURY = 4;
	
	public static final int[] PERPETUAL_CALENDAR_CENTURY_NUMBERS = {6, 4, 2, 0};
	
	public static final int OVERALL_WEEK_DAYS_NUMBER = WeekDays.values().length;
	public static final int OVERALL_MONTHS_NUMBER    = MonthsTable.values().length;	
	public static final int ZERO_REMINDER = 0;	
	public static final int LAST_ONE_DIGIT = 9;
	public static final int ZERO = 0;
	
	public static final String COLON = ":";
	public static final String SPACE = " ";	
    public static final String NEXT_LINE = "\n";		    	
	public static final String HYPHEN = "-";
	
	public static final String ENTER_DATA_MASSAGE = "Please enter";
	public static final String DATA_ENTERED_NOT_CORRECT_MESSAGE = 
			"You have entered invalid data. Please enter positive integer number.";	
	public static final String DATE_HEADER_MESSAGE = 
			"You have entered the next date";
	public static final String VALID_DATE_MESSAGE  = "Your date is valid!";			
	public static final String INVALID_DATE_MESSAGE  = "Your date is invalid!";	
	public static final String WEEKDAY_HEADER_MESSAGE = "The weekday is";	
	public static final String LINES_SEPARATOR = 
			"----------------------------------------------------------------";
	public static final String DATE_FORMAT_TEXT = "([yyyy-mm-dd] format)";	
		
		
	public static void main (String[] args) {	
		
		printDescription(PROGRAM_DESCRIPTION);
				
		int[] inputDayMonthYear = getInputDayMonthYear();	
		
		printInputDate(inputDayMonthYear);
		
	    boolean inputDateValid = checkInputDateValidity(inputDayMonthYear);	
		
	    printInputDateValidityResult(inputDateValid);
				
		if (inputDateValid) {		
			int weekDay = findDayOfWeekUsingPerpetualCalendarFormula(inputDayMonthYear);
			printWeekDayResult(weekDay);
		}
	}
 
	public static int[] getInputDayMonthYear() {	
	
		int[] result = new int[LABELS.length];		
		boolean dataEnteredIsValid;
		String enteredText;
						
		for (int nuberOfInput = 0; nuberOfInput < result.length; nuberOfInput++) {		
			 dataEnteredIsValid = false;
			
			do {		
				enteredText = askUserForInput(LABELS[nuberOfInput]);						
				dataEnteredIsValid = validateDataInput(enteredText);				
			    printInvalidInputStateIfTrue(dataEnteredIsValid);
				if (dataEnteredIsValid) {
					result[nuberOfInput] = Integer.parseInt(enteredText);
				}
			} while (!dataEnteredIsValid);
		}
		
		return result;
	}
	
	public static String askUserForInput(String label) {
		
		Scanner input = new Scanner(System.in);
		
		System.out.println(ENTER_DATA_MASSAGE + SPACE + label + COLON);
		return input.nextLine();
	}
	
	public static boolean validateDataInput(String enteredText) {
						
		try	{
			return
					(Integer.parseInt(enteredText) > 0);				
		}
		catch (NumberFormatException exception) {
			return false;
		}							
	}
	
	public static void printInvalidInputStateIfTrue(boolean dataEnteredIsValid) {
												  
		if (!dataEnteredIsValid) {				
			System.out.println(NEXT_LINE + 
					DATA_ENTERED_NOT_CORRECT_MESSAGE + NEXT_LINE);
		}									  
	}
		
	public static boolean checkInputDateValidity(int[] inputDate) {
			
		int inputMonth = inputDate[1];
		
		if (inputMonth > OVERALL_MONTHS_NUMBER) {
			return false;
		}
		
		boolean isLeapYear = checkIfLeapYear(inputDate[0]);		
		int inputDay = inputDate[2];		
		int monthNumberInLoop;	
		int maximumDaysNumber;
				
		for (MonthsTable monthEnum: MonthsTable.values()) {			
			monthNumberInLoop = monthEnum.getMonthNumber(); 
			maximumDaysNumber = monthEnum.getDaysInMonth();
			
			if (isLeapYear && 
					(monthNumberInLoop == MonthsTable.FEBRUARY.getMonthNumber())) {
				maximumDaysNumber++;
			}
			
			if (monthNumberInLoop == inputMonth) {
				if (inputDay > maximumDaysNumber) {
					return false;
				}
			}
		}		
		return true;		
	}
	
	public static boolean checkIfLeapYear(int yearToCheck) {
			
		if (((yearToCheck % LEAP_YEAR_DIVIDER_NOT_IN_CENTURY) == ZERO_REMINDER) 
				&& ((yearToCheck % CENTURY) != ZERO_REMINDER)) {
			return true;
		} else {
			return 
					(yearToCheck % LEAP_YEAR_DIVIDER_IN_CENTURY) == ZERO_REMINDER;
		}
	}

	public static int findDayOfWeekUsingPerpetualCalendarFormula(int[] dateInput) { 
	
		int dayOfMonth = dateInput[2];
		int month = dateInput[1];
		int year = dateInput[0];			
		
		boolean leapYear = checkIfLeapYear(year);	
		
		int monthInPerpetualCalendar = findMonthsInPerpetualCalendarTable(month, leapYear);
		int lastTwoDigitsOfYear = getLastTwoDigitsOfYear(year);
		int centuryNumber = getCenturyNumber(year);	

		int result = ((dayOfMonth % OVERALL_WEEK_DAYS_NUMBER)
				+ monthInPerpetualCalendar + lastTwoDigitsOfYear
				+ (lastTwoDigitsOfYear / LEAP_YEAR_DIVIDER_NOT_IN_CENTURY)
				+ centuryNumber) % OVERALL_WEEK_DAYS_NUMBER;

		return result;
	}
	
	public static int findMonthsInPerpetualCalendarTable(int monthNumberNormal, 
			boolean leapYear) {
			
		int result = 0;
		int monthNumberInLoop;
		
		for (MonthsTable monthEnum: MonthsTable.values()) {			
			monthNumberInLoop = monthEnum.getMonthNumber(); 
			
			if (monthNumberInLoop == monthNumberNormal) {
				if (leapYear) {
					result = monthEnum.getMonthNumberInPerpetualTableInLeapYear();
				} else {
					result = monthEnum.getMonthNumberPerpetualInTable();
				}
				break;
			}
		}
        return result;			
	}

	public static int getLastTwoDigitsOfYear(int year) {
	
		while (year >= CENTURY) {
			year = year - CENTURY; 
		}
		return year;
	}	
	
	public static int getCenturyNumber(int year) {
	
		int centuryNumber = 0;
		int firstTwoDigits = year;
		
		while (firstTwoDigits >= CENTURY)
			firstTwoDigits = (firstTwoDigits - firstTwoDigits % TEN_YEARS) 
				/ TEN_YEARS;
		
		for (int indexOfCenturyNumber=0; 
				indexOfCenturyNumber < PERPETUAL_CALENDAR_CENTURY_NUMBERS.length; 
				indexOfCenturyNumber++) {
		
			if (((firstTwoDigits - indexOfCenturyNumber)
					% LEAP_YEAR_DIVIDER_NOT_IN_CENTURY) == ZERO_REMINDER) {
				centuryNumber = PERPETUAL_CALENDAR_CENTURY_NUMBERS[indexOfCenturyNumber];
			}				
		}
		return centuryNumber;
	}
	
	public static String convertWeekToString(int weekDay) {
	
		String result = ""; 
		int weekDayInLoop;
		
		for (WeekDays weekDayEnum: WeekDays.values()) {					
			weekDayInLoop = weekDayEnum.getWeekDayNumber(); 
							
			if (weekDayInLoop == weekDay) {
				result = result + weekDayEnum;
			}
		}	
		return result;
	}
	
	public static void printInputDate(int[] inputDate) {
		
	    String resultToPrint = NEXT_LINE  
				+ DATE_HEADER_MESSAGE + COLON + SPACE;				
		
		for (int indexOfInput = 0; indexOfInput < inputDate.length; indexOfInput++) {		
			if (indexOfInput > ZERO) {
				resultToPrint = resultToPrint + HYPHEN;
			}
			
			if (inputDate[indexOfInput] <= LAST_ONE_DIGIT) {
				resultToPrint = resultToPrint + Integer.toString(ZERO);
			}	
			resultToPrint = resultToPrint + inputDate[indexOfInput];	
		}
		
		resultToPrint = resultToPrint + SPACE + DATE_FORMAT_TEXT;
	
		System.out.println(resultToPrint);			
    }
	
	public static void printInputDateValidityResult(boolean dateValid) 	{
	
		if (dateValid) {
			System.out.println(NEXT_LINE + VALID_DATE_MESSAGE);
		} else {
			System.out.println(NEXT_LINE + INVALID_DATE_MESSAGE);
		}
		
	}
		
	public static void printWeekDayResult(int weekDay) {
	
		String weekDayName = convertWeekToString(weekDay);
		weekDayName = weekDayName.substring(0,1) + weekDayName.substring(1).toLowerCase();
		System.out.println(NEXT_LINE + WEEKDAY_HEADER_MESSAGE + SPACE + weekDayName);
	}
	
	public static void printDescription(String description) {
		
		System.out.println(NEXT_LINE + LINES_SEPARATOR);
		System.out.println(NEXT_LINE + SPACE + description);
		System.out.println(LINES_SEPARATOR);
	}
	
	static enum MonthsTable {
	
		JANUARY(1, 31, 0, 6), FEBRUARY(2, 28, 3, 2), MARCH(3, 31, 3, 3),
		APRIL(4, 30, 6, 6), MAY(5, 31, 1, 1), JUNE(6, 30, 4, 4),
		JULY(7, 31, 6, 6), AUGUST(8, 31, 2, 2), SEPTEMBER(9, 30, 5, 5),
		OCTOBER(10, 31, 0, 0), NOVEMBER(11, 30, 3, 3), DECEMBER(12, 31, 5, 5);
		
		MonthsTable(int monthNumber, int daysInMonth,
				int monthNumberInPerpetualTable, int monthNumberInPerpetualTableInLeapYear) {
		
			this.monthNumber = monthNumber;
			this.daysInMonth = daysInMonth;
			this.monthNumberInPerpetualTable = monthNumberInPerpetualTable;
			this.monthNumberInPerpetualTableInLeapYear = monthNumberInPerpetualTableInLeapYear;			
		}
		
		private int monthNumber;
		private int daysInMonth;
		private int monthNumberInPerpetualTable;
		private int monthNumberInPerpetualTableInLeapYear;

		public int getMonthNumber() {
			return monthNumber;
		}
		
		public int getDaysInMonth() {
			return daysInMonth;
		}
		
		public int getMonthNumberPerpetualInTable() {
			return monthNumberInPerpetualTable;
		}
		
		public int getMonthNumberInPerpetualTableInLeapYear() {
			return monthNumberInPerpetualTableInLeapYear;
		}		
	}	
	
	static enum WeekDays {
	
		SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6);
		
		WeekDays(int weekDayNumber) {
		
				this.weekDayNumber = weekDayNumber;
		}	
		
		private int weekDayNumber;
		
		public int getWeekDayNumber() {
			return weekDayNumber;
		}		
	}
}